package javaAdvanced;

import java.lang.reflect.*;
import java.util.Arrays;

/**
 * 类的描述信息
 * 反射的练习
 *
 * @author cuiH
 * @since 1.5
 */
public class ReflectTest {

    /**
     * 反射：将java中各个成分 映射到相应的java类上面
     * 就是说你想去到java类的一些信息，就需要用到反射。(类信息层面的控制)
     */

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, ClassNotFoundException {

        //constructor类（构造方法的反射）
        Constructor constructor = String.class.getConstructor(StringBuffer.class);
        String strTest = (String) constructor.newInstance(new StringBuffer("cuiHuan")); //调用该方法时候也需要声明类型

        System.out.println(strTest.charAt(3));        //运行时刻该constructor对应的是StringBuffer, constructor在编译运行时刻进行转换，泛型进行约束
        /**
         * 作用，得到每个类的构造方法
         * 然后调用该方法下面的构造器
         * 正常情况下是：class——》constructor->new object
         * 使用constructor->调用 constructor，调用obj
         */


        //field类的使用（字段）,反射调用字段
        ReflectPoint pt1 = new ReflectPoint(3, 5);
        Field fieldY = pt1.getClass().getField("y");         //得到字节码，得到field,field是类上的某个域，去取相应对象上的该域
        fieldY.get(pt1);                //去到数值
        System.out.println(fieldY.get(pt1));

        /**
         * result：java.lang.NoSuchFieldException 若是getFiled访问私有方法时：报错没有改域
         * 对于私有需要用 getDeclaredField
         */
        Field fieldX = pt1.getClass().getDeclaredField("x");
        fieldX.setAccessible(true);
        System.out.println(fieldX.get(pt1));
        /**
         * result:java.lang.IllegalAccessException  提示非法错误，可以进行去到，但是仍然无法用，
         * 需要暴力输出:fieldX.setAccessible(true);  强制设定为可以进入（暴力进入）
         */

        //利用反射，进行对源程序进行修改。例如修改已经打完jar包的程序。
        changeStringValueBToA(pt1);
        Field[] fieldStr = pt1.getClass().getFields();
        System.out.println("b变为a更改后为：");
        for (Field field : fieldStr) {
            System.out.println(field.get(pt1));
        }


        //Method 类 反射调用方法  (方法调用方法，invoke调用该方法)
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        methodCharAt.invoke(strTest, 3);
        System.out.println(methodCharAt.invoke(strTest, 3));
        /**
         * 获取方法（踩刹车）
         * 调用方法（刹车调用停车的方法），面向对象的设计
         * 相当于获取了静态方法，静态引入
         * 注意jdk1.4之前没有可变参数，
         */
        System.out.println(methodCharAt.invoke(strTest, new Object[]{3}));//jdk 1.4的做法，声明一个数组来代替可变参数（原始方法）


        TestArguments.main(new String[]{"cuiHuan", "like", "java"});    //调用main方法,原有的静态的调用方法
        String className = "javaAdvanced.TestArguments";
        Method mainMethod = Class.forName(className).getMethod("main", String[].class);
        mainMethod.invoke(null, new Object[]{new String[]{"cuiHuan", "like", "java"}});

        //数组的反射
        int[] a1 = new int[]{2, 1, 3};
        int[] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String[] a5 = new String[2];
        String[] a6 = new String[3];
        String[] a4 = new String[]{"a", "b", "c"};
        System.out.println("数组比较：" + (a1 == a2));         //没有赋值之前为 true  ；赋值之后为false
        System.out.println("String比较：" + (a5 == a6));         //没有赋值之前为 true  ；赋值之后为false
        System.out.println("a1字节码的名字：" + a1.getClass().getName());
        System.out.println("a2字节码的名字：" + a2.getClass().getName());
        System.out.println("数组字节码比较：" + (a1.getClass() == a2.getClass()));  //说明用的是一个字节码
        System.out.println("一维数组的父类的字节吗的名字：" + a1.getClass().getSuperclass().getName());  //数组的父类是obj
        System.out.println("二维数组的父类的字节吗的名字：" + a3.getClass().getSuperclass().getName());  //数组的父类是obj
        System.out.println("String的父类的字节吗的名字：" + a4.getClass().getSuperclass().getName());  //数组的父类是obj

        Object obj1 = a1;  //数组其实是一个对象
        Object obj2 = a4;
        Object[] obj3 = a4;  //二维数组其实是一组对象
        Object[] obj4 = a3;
        System.out.println("直接输出a1:" + a1);             //result:   直接输出a1:[I@16c9ba38        类型为I(int) 具体数值为@16c9ba38
        System.out.println("输出为string的a1：" + Arrays.toString(a1));             //result:输出为string的a1：[2, 1, 3]
        System.out.println("输出为list的a1：" + Arrays.asList(a1));           //result: 输出为list的a1：[[I@15e0be38]   证书看做了一个参数（相当于一个obj）
        System.out.println("直接输出数组：" + a4);            //result:直接输出数组:  [Ljava.lang.String;@95c7850
        System.out.println("输出为list的a1：" + Arrays.asList(a4));  //字符串可以成功转换为Llist

        /**
         * 之前可以发现，数组的本质一个对象，二维相当于一组对象，对于asList获取的是对象的地址
         * 当数组或者String没有初始化之前，他们共用一个字节码。（object）
         *
         * 数组反射的应用
         * 现在如果要改变数组的其中一个元素
         */
        System.out.println("数组反射的应用：打印数组中的每个元素");
        printObject(new int[]{2, 1, 3});           //result:2  1  3
        printObject(new int[4]);   //result: 0  0  0  0   默认数值都是0
        printObject("String");     //result:String


    }

    //打印数组的函数
    private static void printObject(Object obj) {
        Class cls = obj.getClass();
        if (cls.isArray()) {
            int length = Array.getLength(obj);
            for (int i = 0; i < length; i++) {
                System.out.print(Array.get(obj, i) + "  ");
            }
        } else {
            System.out.println(obj);
        }

    }

    //有许多String类型，将所有String中的b 变为 a
    private static void changeStringValueBToA(Object obj) throws IllegalAccessException {
        Field[] fields = obj.getClass().getFields();
        for (Field field : fields) {
            //判定是String的字节码
            if (field.getType() == (String.class)) {   //字节码最好不用Equals比 较，双等号比较好，因为字节码只有一个，唯一
                String oldValue = (String) field.get(obj);
                String newValue = oldValue.replace('b', 'a');
                field.set(obj, newValue);
            }
        }
    }


}

//写一个方法，调用main方法
class TestArguments {
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}